﻿using BoardGame;
using System;
using System.Collections;
using System.Collections.Generic;


namespace GameModelView
{
    [FlagsAttribute]
    public enum GameStatus
    {
        Go = 0, Temp = 1, Setup = 2,
        Check = 4, NoMoves = 8, Win = 16, Tie = 32, Loss = 64,
        Checkmate = 4 + 8 + 64, Stalemate = 8 + 32,
        Resign = Loss + 128, NoCaptures = Tie + 256, Repetitive = Tie + 512
    }


    public enum Token { Empty, King, Queen, Rook, Bishop, Knight, Pawn }


    public delegate void GameCreatedEventHandler ();
    public delegate void StatusUpdateEventHandler ();
    public delegate void TokenRemoveEventHandler (int x, int y);
    public delegate void TokenPlaceEventHandler (int x, int y, Player player, Token token);
    public delegate void MoveProcessEventHandler ();
    public delegate void UndoPreviewEventHandler ();
    public delegate void HiliteChangeEventHandler (int x, int y, bool newHilite);
    public delegate void HilitesClearEventHandler ();


    /// <summary>
    /// Represent the readonly base of a chess game model.
    /// </summary>
    /// <remarks>
    /// This class provides readonly access to the game (including event notifications).
    /// This class is intended to be visible to the View of the MVC pattern.
    /// </remarks>
    public class ChessModelView
    {
        public event GameCreatedEventHandler GameCreated;
        public event StatusUpdateEventHandler StatusUpdate;
        public event TokenRemoveEventHandler TokenRemove;
        public event TokenPlaceEventHandler TokenPlace;
        public event MoveProcessEventHandler MoveProcess;
        public event UndoPreviewEventHandler UndoPreview;
        public event HiliteChangeEventHandler HiliteChange;
        public event HilitesClearEventHandler HilitesClear;

        internal GameStatus firstStatus;


        public Board Board { get; private set; }

        /// <summary>
        /// Returns <b>true</b> if moving player may legally request a draw based on the
        /// fifty-move rule; else returns false.
        /// </summary>
        public bool Is50MoveRuleClaimable
        { get; protected set; }

        public Type[] Promotions { get; internal set; }
        public bool Same6Moves { get; internal set; }

        public PieceLists Pieces;
        public PlyStack Plies;


        internal ChessModelView ()
        {
            Clear ();
        }

        public void OnGameCreated (ChessModelView chessGame)
        { if (GameCreated != null) GameCreated (); }

        public void OnStatusUpdate ()
        { if (StatusUpdate != null) StatusUpdate (); }

        public void OnTokenPlace (int x, int y, Player player, Token token)
        { if (TokenPlace != null) TokenPlace (x, y, player, token); }

        public void OnTokenRemove (int x, int y)
        { if (TokenRemove != null) TokenRemove (x, y); }

        public void OnPieceMoved ()
        { if (MoveProcess != null) MoveProcess (); }

        public void OnUndoPreview ()
        { if (UndoPreview != null) UndoPreview (); }

        public void OnHiliteChange (int x, int y, bool newHilite)
        { if (HiliteChange != null) HiliteChange (x, y, newHilite); }

        public void OnHilitesClear ()
        { if (HilitesClear != null) HilitesClear (); }


        protected void Clear ()
        {
            if (Pieces == null)
                Pieces = new PieceLists ();
            else
                Pieces.Clear ();

            if (Plies == null)
                Plies = new PlyStack (this);
            else
                Plies.Clear ();

            if (Board == null)
                Board = new Board ();
            else
                Board.Clear ();

            if (Promotions == null || Promotions.Length != 2)
                Promotions = new Type[2];

            Promotions[0] = typeof (Queen);
            Promotions[1] = typeof (Queen);

            Same6Moves = false;
            Is50MoveRuleClaimable = false;
            firstStatus = GameStatus.Setup;
        }


        public GameStatus Status
        { get { return Plies.Count > 0 ? Plies.Top.PlyStatus : firstStatus; } }

        public bool IsUnderway
        { get { return (Status & (GameStatus.Win | GameStatus.Tie | GameStatus.Loss | GameStatus.Setup)) == 0; } }

        public Player PlayerTurn
        { get { return (Player) (Plies.Count % 2); } }

        public bool IsMovable (int x, int y)
        { return Board[x, y].IsPlayer (PlayerTurn); }

        public string CurrentPlayerPromotionName
        { get { return Promotions[(int) PlayerTurn].Name; } }


        public IEnumerable GetBoardPieces (Player player)
        {
            // No foreach here because promotions modify collection.
            for (int k = 0; k < Pieces[player].Count; ++k)
            {
                Piece piece = Pieces[player][k];
                if (!piece.IsCaptured)
                    yield return piece;
            }
        }


        protected bool GetIs50MoveRuleClaimable ()
        {
            int repeat = 100;

            if (Plies.Count < repeat)
                return false;

            for (int k = Plies.Count - 1; k >= Plies.Count - repeat; --k)
                if (Plies[k].Captured != null || Plies[k].Moving is Pawn || Plies[k].Promotion != null)
                    return false;

            return true;
        }


        // TODO rename IsSame6Moves to GetIs3FoldRepetitionClaimable and fix
        internal bool IsSame6Moves ()
        {
            int repeat = 6;

            if (Plies.Count < repeat + 2)
                return false;

            for (int k = Plies.Count - 1; k >= Plies.Count - repeat + 2; --k)
                if (Plies[k].From.X != Plies[k - 4].From.X || Plies[k].To.Y != Plies[k - 4].To.Y)
                    return false;

            return true;
        }


        internal void SetStatus (GameStatus newStatus)
        {
            if (Plies.Count == 0)
                this.firstStatus = newStatus;
            else
                Plies.Top.PlyStatus = newStatus;
        }


        internal GameStatus CalcStatus ()
        {
            bool threatened = IsKingThreatened (PlayerTurn);

            foreach (Piece piece in GetBoardPieces (PlayerTurn))
                foreach (Coordinate place in piece.GetLegalMoves())
                    if (threatened)
                        return GameStatus.Check;
                    else
                        return GameStatus.Go;

            if (threatened)
                return GameStatus.Checkmate;
            else
                return GameStatus.Stalemate;
        }


        internal bool IsKingThreatened (Player kingPlayer)
        {
            foreach (Piece piece in GetBoardPieces (ChessModelView.Opponent (kingPlayer)))
                foreach (Coordinate move in piece.GetMoves())
                    if (Board[move.X, move.Y] is King)
                        return true;

            return false;
        }


        public string[] GetStatus ()
        {
            ChessModelView game = this;
            string TurnText, Status1 = "", Status2 = "";

            GameStatus newStatus = game.Status;

            if (game.IsUnderway)
                TurnText = game.PlayerTurn.ToString ();
            else if ((newStatus & GameStatus.Setup) != 0)
                TurnText = "";
            else
                TurnText = "GAME OVER";

            if (newStatus == GameStatus.Check)
                Status1 = "Check!";
            else if ((newStatus & GameStatus.Win) != 0)
                Status1 = game.PlayerTurn.ToString () + " Wins!";
            else if ((newStatus & GameStatus.Loss) != 0)
            {
                if (newStatus == GameStatus.Checkmate)
                    Status1 = "Checkmate";
                else if ((newStatus & GameStatus.Resign) == GameStatus.Resign)
                    Status1 = "Resignation";
                Status2 = PlayerName (Opponent (PlayerTurn)) + " Wins!";
            }
            else if ((newStatus & GameStatus.Tie) != 0)
            {
                Status1 = "Draw";
                if (newStatus == GameStatus.Stalemate)
                    Status2 = "Stalemate";
                else if (newStatus == GameStatus.NoCaptures)
                    Status2 = "No captures";
                else if (newStatus == GameStatus.Repetitive)
                    Status2 = "3 turns same";
            }
            else
                Status1 = "Go";

            string[] retVal = new string[3] { TurnText, Status1, Status2 };

            return retVal;
        }



        public static Player Opponent (Player player)
        {
            return (Player) (1 - player);
        }


        public static string PlayerName (Player player)
        {
            return player == Player.White? "White" : "Black";
        }
    }
}
